# -- coding: utf-8 --
import mip
from mip import Model, CONTINUOUS, BINARY, xsum, maximize
import pandas as pd
import networkx as nx
from matplotlib import pyplot as plt


# 路网
class Network:
    def __init__(self):  # list: ['北京', '天津', '济南', '南京', '上海']
        self.NodeList = []
        self.EdgeList = []
        self.PrevEdge = {}
        self.NextEdge = {}

    def AddNodes(self, list):
        for name in list:  # name: '北京'->'天津'->'济南'->'南京'->'上海'
            if len(self.NodeList) == 0:  # 第一次len(self.StationList)是等于0的，后面都大于0
                self.NodeList.append(name)  # 将name—>北京添加进StationList
            # 返回for name in list，此时name变成天津
            else:
                last = self.NodeList[len(self.NodeList) - 1]  # last:北京
                node = name  # name天津赋给station—name济南赋给station
                edge = Edge(last, node)
                self.NodeList.append(node)  # StationList变为：北京、天津
                self.EdgeList.append(edge)
                self.PrevEdge[node] = edge
                self.NextEdge[last] = edge
        for index1 in range(len(self.NodeList)):
            if self.NodeList[index1]:
                node1 = self.NodeList[index1]
                for index2 in range(index1 + 1, len(self.NodeList)):
                    if self.NodeList[index2]:
                        self.ODList.append(Edge(node1, self.NodeList[index2]))

    # 通过车站名称获取区间
    def GetEdge(self, sta1, sta2):
        for sec in self.EdgeList:
            if sec.start_station == sta1 and sec.end_station == sta2:
                return sec

    # 增加区间
    def AddEdge(self, s_node, e_node, distance):
        if s_node not in self.NodeList:
            self.NodeList.append(s_node)
        if e_node not in self.NodeList:
            self.NodeList.append(e_node)
        for edge in self.EdgeList:
            if s_node == edge.start_station and e_node == edge.end_station:
                return
        edge = Edge(s_node, e_node, distance)
        self.EdgeList.append(edge)
        if e_node not in self.PrevEdge.keys():
            self.PrevEdge[e_node] = [edge]
        else:
            self.PrevEdge[e_node].append(edge)
        if s_node not in self.NextEdge.keys():
            self.NextEdge[s_node] = [edge]
        else:
            self.NextEdge[s_node].append(edge)

    # 获取路径长度
    def GetDistance(self, route):
        distance = 0
        for index in range(len(route.List)-2):
            edge = self.GetEdge(route.List[index], route.List[index+1])
            distance += edge.Distance
        return distance


# 弧段
class Edge:
    def __init__(self, start_node, end_node, distance):
        self.start_station = start_node
        self.end_station = end_node
        self.Distance = distance


# 路径
class Route:
    def __init__(self, *list):
        self.List = list

    def HasOverlapped(self, edge):
        for index in (0, len(self.List) - 2):
            if self.List[index] == edge.start_station and self.List[index + 1] == edge.end_station:
                return True
        return False


# 各产品的运输需求
class CargoOrder:
    def __init__(self):
        self.Id = 0  # 编号
        self.Origin = ''  # 出发地
        self.Destination = ''  # 目的地
        self.Weight = 0  # 重量
        self.Max_Acceptable_Price = 0  # 最高能接受价格
        self.Order_Day = 0  # 预定日期（距离决策日期）
        self.Route = Route()  # 订单路径


class Parameters:
    pass


def glb_generate_test_world():
    net = Network()
    net.AddEdge('s1', 's2', 500)
    net.AddEdge('s2', 's3', 600)
    net.AddEdge('s3', 'a1', 0.01)
    net.AddEdge('s3', 's2', 600)
    net.AddEdge('a1', 'a2', 600)
    net.AddEdge('a2', 'b1', 900)
    net.AddEdge('b1', 'b2', 800)
    net.AddEdge('b2', 'a1', 1000)
    net.AddEdge('s2', 's1', 500)
    net.AddEdge('a1', 's3', 0.01)
    param = Parameters()
    param.Num_of_Boxes = 150  # 运力
    param.Horizon = 3  # 编制未来几天的计划
    param.Min_Price = 10  # 最低运价（协议价格）         ？改成列表数据读取
    param.Box_Capacity = 30  # 箱子容量
    param.Restrict_Node = []  # 限制拼箱节点
    return net, param


def glb_read_order():
    orderList = []
    file = "data20221029.xlsx"
    data = pd.read_excel(file, sheet_name=0)  # reading file
    nrows = data.shape[0]
    for rowIndex in range(nrows):
        order = CargoOrder()
        order.Id = data.iloc[rowIndex, 0]
        order.Order_Day = data.iloc[rowIndex, 1]
        order.Order_day=data.iloc[rowIndex, 6]
        order.Weight = data.iloc[rowIndex, 2]
        order.Origin = data.iloc[rowIndex, 3]
        order.Destination = data.iloc[rowIndex, 4]
        order.Expect = data.iloc[rowIndex, 9]
        order.Route = Route()
        orderList.append(order)
    return orderList


def glb_solve_mip(problem_input, orderList, param):
    model = Model()

    allocation_var = {(order, box, day): model.add_var(var_type=BINARY) for order in orderList for box
                      in range(param.Num_of_Boxes) for day in range(param.Horizon)}

    p_var = {day: model.add_var(lb=0, ub=9999, var_type=CONTINUOUS) for day in range(param.Horizon)}

    aux_var = {(order, box, day): model.add_var(var_type=CONTINUOUS) for order in orderList for box
               in range(param.Num_of_Boxes) for day in range(param.Horizon)}
    # auy_var = {( box, day): model.add_var(var_type=CONTINUOUS)  for box in range(param.Num_of_Boxes) for day
    #            in range(param.Horizon)}


    # 约束1：容量约束
    for edge in problem_input.EdgeList:
        for box in range(param.Num_of_Boxes):
            for day in range(param.Horizon):
                model += xsum(order.Weight * order.Route.HasOverlapped(edge) * allocation_var[order, box, day] for order
                              in orderList) <= param.Box_Capacity

    # 约束2：限制拼箱节点
    for box in range(param.Num_of_Boxes):
        for day in range(param.Horizon):
            model += xsum(
                (order.Origin in param.Restrict_Node) * allocation_var[order, box, day] for order in orderList) <= 1

    # 约束3：运价不能超预期
    for order in orderList:
        for box in range(param.Num_of_Boxes):
            for day in range(param.Horizon):
                model += aux_var[order, box, day] * problem_input.GetDistance(order.Route) * order.Weight \
                         - order.Expect * allocation_var[order, box, day] >= 0

    # 约束4：运价不能低于协议运价
    for day in range(param.Horizon):
        model += p_var[day] >= param.Min_Price

    # 约束 Auxiliary variables
    for order in orderList:
        for box in range(param.Num_of_Boxes):
            for day in range(param.Horizon):
                model += aux_var[order, box, day] <= order.Expect * allocation_var[order, box, day]
                model += aux_var[order, box, day] <= p_var[day]
                model += aux_var[order, box, day] >= p_var[day] - order.Expect * (
                            1 - allocation_var[order, box, day])

    # 约束extra-1：订单不能分开运
    for order in orderList:
        model += xsum(
            allocation_var[order, box, day] for box in range(param.Num_of_Boxes) for day in range(param.Horizon)) <= 1

    # 目标
    #model.objective = maximize(xsum(aux_var[order, box, day] * problem_input.GetDistance(order.Route) * order.Weight
                                  #  for order in orderList for box in range(param.Num_of_Boxes) for day in
                                  #  (range(param.Horizon))-50(xsum(aux_var[order, day] * order.Order_day)
                                  #  for order in orderList  for day in (range(param.Horizon)-100(xsum(auy_var[box, day]*2*order.Weight)))
                                  #  for box in range(param.Num_of_Boxes) for day in range(param.Horizon)))) # 还要减去惩罚项
    model.objective = maximize(xsum(aux_var[order, box, day] * problem_input.GetDistance(order.Route) * order.Weight
                                    for order in orderList for box in range(param.Num_of_Boxes) for day in
                                    (range(param.Horizon)))
                               - 10*xsum(aux_var[order,box,day] * order.Order_day for order in orderList for box in
                                     range(param.Num_of_Boxes) for day in (range(param.Horizon))))
                                 #       - 100*  xsum(auy_var[box, day] * 2 * order.Weight
                                 # for box in range(param.Num_of_Boxes) for day in
                                 # (range(param.Horizon))))  # 还要减去惩罚项
    # 求解
    # model.write("model.lp")
    model.max_mip_gap = 0.01
    # model.verbose = 0
    model.optimize(max_seconds=300)

    # 输出
    if model.num_solutions:
        for order in orderList:
            for box in range(param.Num_of_Boxes):
                for day in range(param.Horizon):
                    if allocation_var[order, box, day].x == 1:
                        print('订单:{0},分配箱子{1},出发日期:{2}'.format(order.Id, box, day))
        for day in range(param.Horizon):
            print('价格:{0}'.format(p_var[day].x))
        print('目标函数值:', format(model.objective_value))


def glb_finding_path(problem_input, orderList):
    G = nx.DiGraph()
    for edge in problem_input.EdgeList:
        G.add_edge(edge.start_station, edge.end_station, weight=edge.Distance)
    for order in orderList:
        path = nx.dijkstra_path(G, order.Origin, order.Destination, "distance")
        order.Route = Route(path)
    nx.draw(G, with_labels=True)
    plt.title('Continue in 5 seconds')
    plt.axis('on')
    plt.xticks([])
    plt.yticks([])
    plt.savefig('路网图.png')


if __name__ == '__main__':
    Problem_input, parameters = glb_generate_test_world()  # 线路上的停站
    OrderList = glb_read_order()
    glb_finding_path(Problem_input, OrderList)
    glb_solve_mip(Problem_input, OrderList, parameters)
